﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.IO;

namespace FixEngine
{
    public class FixMessageFormatter: IFormatter 
    {
        SerializationBinder binder;
        StreamingContext context;
        ISurrogateSelector surrogateSelector;

        #region IFormatter Members

        public SerializationBinder Binder
        {
            get
            {
                return binder;
            }
            set
            {
                binder = value;
            }
        }

        public StreamingContext Context
        {
            get
            {
                return this.context;
            }
            set
            {
                this.context = value;
            }
        }

        public object Deserialize(System.IO.Stream serializationStream)
        {
            if (!serializationStream.CanRead)
            {
                throw new IOException("The stream is not readable");
            }

            int b = serializationStream.ReadByte();
            StringBuilder tagIDBuilder = new StringBuilder(5);
            StringBuilder tagValueBuilder = new StringBuilder(20);
            int tagid = -1;

            FixVersion fixver = FixVersion.Unknown;
            //string checksum = null;
            string msgtype = null;
            IMessage msgInstance = null;

            int iBodylength = -1;
            string strChecksum = null;
            int isum = 0;
            bool bTagID = true;
            bool bTagValue = false;
            int byteCount = 0;  //to compare with bodylength
            bool bBodyLengthCount = false;  //enable body length
            bool bChecksum = true; //enable checksum

            while (b != -1)
            {
                if (b == '=')
                {
                    bTagID = false;
                    bTagValue = true;

                    tagid = Int32.Parse(tagIDBuilder.ToString());
                    tagIDBuilder.Length = 0;

                    if (tagid == 10)    //checksum
                    {
                        bChecksum = false;
                        bBodyLengthCount = false;
                        byteCount -= 3; // '1','0',SOH after bodylength
                        isum -= (int)'1';
                        isum -= (int)'0';
                    }
                }
                else if (b == Utility.SOH)
                {
                    bTagID = true;
                    bTagValue = false;
                    string tagvalue=tagValueBuilder.ToString();
                    if (tagid != -1)
                    {
                        if (tagid == 8) //BeginString
                        {
                            fixver = Utility.ConvertBeginString2FixVersion(tagvalue);
                        }
                        else if (tagid == 9)    //BodyLength
                        {
                            iBodylength = Int32.Parse(tagvalue);
                            bBodyLengthCount = true;
                        }
                        else if (tagid == 35)   //msgtype
                        {
                            msgtype = tagvalue;
                            if (fixver != FixVersion.Unknown)
                                msgInstance = MessageFactory.CreateMessage(fixver, msgtype);
                        }
                        else if (tagid == 10)   //Checksum
                        {
                            strChecksum = tagvalue;
                        }
                        else
                        {
                            IField fieldInstance = FieldFactory.CreateField(fixver,tagid, tagvalue);
                            if (msgInstance != null)
                                msgInstance.Content.Add(fieldInstance);
                        }

                    }
                    tagValueBuilder.Length = 0;
                    tagid = -1;
                }
                else
                {
                    if (bTagID)
                    {
                        tagIDBuilder.Append((char)b);
                    }
                    else if (bTagValue)
                    {
                        tagValueBuilder.Append((char)b);
                    }
                }
                if (bBodyLengthCount)
                    byteCount++;
                if(bChecksum)
                    isum += (int)b;

                b = serializationStream.ReadByte();
            }
            //check validity
            if (iBodylength != byteCount)
                throw new FixEngineException(string.Format("Invalid message body length {0}, expect {1}", iBodylength, byteCount));
            //check sum
            if ((isum % 256).ToString("000") != strChecksum)
                throw new FixEngineException(string.Format("Invalid message checksum {0}, expect {1}", isum%256, strChecksum));


            return msgInstance;
        }

        public void Serialize(System.IO.Stream serializationStream, object graph)
        {
            if (!serializationStream.CanWrite)
            {
                throw new IOException("The stream is not writable");
            }
            if (!(graph is IMessage))
            {
                throw new SerializationException("the object is unrecognizable");
            }
            StringBuilder sbResult = new StringBuilder(100);
            StringBuilder sbPostResult = new StringBuilder(50);
            IMessage msg = (IMessage)graph;
            sbResult.AppendFormat("8={0}{1}",Utility.ConvertFixVersion2BeginString(msg.Version), Utility.SOH);
            
            int bodylength = 0;
            string fieldResult = string.Format("35={0}{1}", msg.MessageType, Utility.SOH);
            sbPostResult.AppendFormat(fieldResult);
            bodylength += Encoding.ASCII.GetByteCount(fieldResult);
            foreach (IField field in msg.Content)
            {
                fieldResult = string.Format("{0}={1}{2}", field.TagID, field.Value, Utility.SOH);
                //TODO::check if the field is optional (value is empty)
                sbPostResult.AppendFormat(fieldResult);

                bodylength += Encoding.ASCII.GetByteCount(fieldResult);
            }
            sbResult.AppendFormat("9={0}{1}",bodylength , Utility.SOH);
            string result=sbResult.ToString()+sbPostResult.ToString();
            byte[] bytes=Encoding.ASCII.GetBytes(result);
            serializationStream.Write(bytes,0,bytes.Length);
            result=string.Format("10={0}{1}", Utility.GenerateCheckSum(result, result.Length), Utility.SOH);
            bytes = Encoding.ASCII.GetBytes(result);
            serializationStream.Write(bytes, 0, bytes.Length);
        }

        public ISurrogateSelector SurrogateSelector
        {
            get
            {
                return this.surrogateSelector;
            }
            set
            {
                this.surrogateSelector = value;
            }
        }

        #endregion
    }
}
